﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Kokomo.IO
{
	[DebuggerDisplay("Process = {Process}")]
	public class ProcessMemoryStream : Stream
	{
		public Process Process { get; private set; }

		public ProcessMemoryStream(Process process)
		{
			ArgumentValidation.CheckNullArgument("process", process);
			this.Process = process;
		}
		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);
		}

		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanSeek
		{
			get { return true; }
		}

		public override bool CanWrite
		{
			get { return true; }
		}

		public override void Flush()
		{
		}

		public override long Length
		{
			get { return uint.MaxValue; }
		}

		public override long Position { get; set; }

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [param: MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
		public override int Read(byte[] buffer, int offset, int count)
		{
			byte[] callBuffer = new byte[count];
			int bytesRead;
			bool result = ReadProcessMemory(this.Process.Handle, new IntPtr(this.Position), callBuffer, callBuffer.Length, out bytesRead);
			if (!result)
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}

			Array.Copy(callBuffer, 0, buffer, offset, bytesRead);

			this.Seek(bytesRead, SeekOrigin.Current);
			return bytesRead;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			long newPosition = this.Position;
			switch (origin)
			{
				case SeekOrigin.Begin:
					newPosition = offset;
					break;
				case SeekOrigin.Current:
					newPosition = this.Position + offset;
					break;
				case SeekOrigin.End:
					newPosition = this.Length + offset;
					break;
			}

			this.Position = newPosition;

			return this.Position;
		}

		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [param: MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
		public override void Write(byte[] buffer, int offset, int count)
		{
			byte[] callBuffer = new byte[count];
			Array.Copy(buffer, offset, callBuffer, 0, count);
			int bytesWritten;
			bool result = WriteProcessMemory(this.Process.Handle, new IntPtr(this.Position), callBuffer, callBuffer.Length, out bytesWritten);
			if (!result)
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}

			this.Seek(bytesWritten, SeekOrigin.Current);
		}
	}
}
